import type { ActionState, AtomLike } from '../core'
import {
  _enqueue,
  action,
  bind,
  isAction,
  isAtom,
  isConnected,
  top,
  withMiddleware,
} from '../core'
import type { Fn } from '../utils'
import { isAbort, isBrowser } from '../utils'
import { getSerial, getStackTrace, isSkip } from './getStackTrace'

let maybeAtomLog = (thing: any) =>
  isAtom(thing)
    ? `[${isAction(thing) ? 'Action' : 'Atom'} ${thing.name}]`
    : thing

/**
 * A special logging action for debugging Reatom applications.
 *
 * `log` provides an enhanced logging experience with automatic tracing and
 * production-safe output. It forwards all arguments to the native `console.log`
 * while providing additional context about the call stack and dependencies.
 *
 * ## Key Benefits
 *
 * - **Short and handy name** - Easy to type and use throughout your codebase
 * - **Automatic stack tracing** - Shows the relative call stack each time it's
 *   called
 * - **Use it everywhere** - Logs are only visible when `connectLogger()` is
 *   active
 * - **Production-safe** - Logs won't appear in production builds when logger is
 *   not connected
 * - **Context-aware** - Integrates with Reatom's dependency tracking system
 * - **Extendable** - You can extend it with other extensions to add custom
 *   behavior
 *
 * @example
 *   import { log } from '@reatom/core'
 *
 *   // Make LOG available globally (recommended)
 *   declare global {
 *     var LOG: typeof log
 *   }
 *   globalThis.LOG = log
 *
 *   // Use anywhere in your code
 *   const myAtom = atom((ctx) => {
 *     const value = ctx.spy(someAtom)
 *     LOG('Current value:', value)
 *     return value * 2
 *   })
 *
 * @example
 *   // In actions
 *   const myAction = action((ctx, payload) => {
 *     LOG('Action called with:', payload)
 *     // ... action logic
 *   })
 *
 * @example
 *   // Multiple arguments like console.log
 *   LOG('Debug info:', { foo: 'bar' }, [1, 2, 3])
 *
 * @example
 *   // Extend LOG with custom behavior using withCallHook
 *   import { withCallHook } from '@reatom/core'
 *
 *   LOG.extend(
 *     withCallHook((ctx, params) => {
 *       // Send logs to a remote service
 *       sendToAnalytics({ level: 'debug', args: params })
 *     }),
 *   )
 *
 * @see {@link connectLogger} - Must be called to enable logging output
 */
export let log = /* @__PURE__ */ (() => {
  return action<any[]>((...args) => args, 'LOG')
})()

let isNewLogStack = true

/**
 * Sets up and connects a logger to the Reatom system for debugging and tracing.
 *
 * This function enhances all non-private atoms and actions with logging
 * capabilities. When an atom's value changes or an action is called, it logs
 * the event with relevant information to the console including:
 *
 * - Previous and current state for atoms
 * - Parameters and return values for actions
 * - Complete dependency stack traces
 * - Error information when exceptions occur
 *
 * The logger adapts to the environment, using different formatting for browser
 * and Node.js. Private atoms (those with names starting with '_' or containing
 * '._') are not logged.
 *
 * @example
 *   // Connect the logger at application startup
 *   import { connectLogger } from '@reatom/core'
 *
 *   connectLogger()
 *
 * @returns {void}
 */
export let connectLogger = () => {
  let isNodeEnv = !isBrowser()

  let logExt = <T extends AtomLike>(target: T): T => {
    if (isSkip(target)) return target

    let title = `%c ${target.name}`
    let isOnReject = target.name.endsWith('.onReject')
    let isOnFulfill = target.name.endsWith('.onFulfill')
    let style = ''
    let abortStyle =
      'font-size: 10px; font-weight: 400; background: #F0F0F020; color: #F0F0F070'
    let errorStyle = 'background: tomato;'
    if (isNodeEnv) {
      let nodeReactiveStyle = '\x1b[44m\x1b[37m' // blue background, white text
      let nodeActionStyle = '\x1b[43m\x1b[30m' // yellow background, black text
      let nodeResetStyle = '\x1b[0m'
      title = `${target.__reatom.reactive ? nodeReactiveStyle : nodeActionStyle} ${target.name} ${nodeResetStyle}`
      abortStyle = '\x1b[103m\x1b[90m' // light yellow background, gray text
      errorStyle = '\x1b[101m\x1b[30m' // bright red (tomato-like) background, black text
    } else {
      let color = target.__reatom.reactive
        ? 'background: #151134; color: white;'
        : 'background: #ffff80; color: #151134;'
      style = `${color}font-size: 12px; font-weight: 600; padding: 0.15em;  padding-right: 1ch;`
    }

    let logStack = (payload: any, error: any, cb: Fn) => {
      try {
        const isAborted = isAbort(error)
        if (isNewLogStack) {
          isNewLogStack = false
          setTimeout(() => {
            isNewLogStack = true
          })
          console.log('--- ' + new Date().toISOString() + ' ----')
        }
        let _title = title
        let _style = style
        if (isAborted) {
          _title = `AbortError: ${error.message} ${title}`
          if (!isNodeEnv) _title = `%c ${_title.replace('%c ', '')}`
          _style += abortStyle
        } else if (error) {
          _style += errorStyle
        }
        console.groupCollapsed(`${_title}${getSerial()}`, _style)
        if (isNodeEnv) {
          if (target === log && !error) {
            console.log(...payload)
          } else if (!isAborted) {
            console.log(error ?? maybeAtomLog(payload))
          }
        }
        cb()
        if (!isNodeEnv && target !== log) console.log('frame:', top())
        const stack = getStackTrace()
        if (stack && stack.includes('\n')) {
          console.log(stack)
        }
        console.groupEnd()

        if (!isNodeEnv) {
          if (target === log && !error) {
            console.log(...payload)
          } else if (!isAborted) {
            console.log(error ?? maybeAtomLog(payload))
          }
        }
      } catch (error) {
        console.log('Reatom log error:', error)
      }
    }

    let initKey = {}

    return target.extend(
      withMiddleware(
        () =>
          function logger(next, ...params) {
            // enqueue log BEFORE `next` call to arrange logs with the order of atoms and actions call
            _enqueue(
              bind(() => {
                if (target.__reatom.reactive) {
                  if (Object.is(prevState, state)) return

                  let inits = top().root.inits
                  if (!inits.has(initKey)) {
                    inits.set(initKey, null)
                    if (params.length === 0) return
                  }

                  logStack(state, error, () => {
                    console.log('new  state:', maybeAtomLog(state))
                    console.log('prev state:', maybeAtomLog(prevState))
                    console.log('connected:', isConnected(target))
                  })
                } else {
                  let call = (state as ActionState)[state.length - 1]

                  if (isOnReject && call) {
                    error = call.payload?.error
                  }

                  if (error) {
                    logStack(undefined, error, () =>
                      params.forEach((param, i) =>
                        console.log(`param ${i + 1}:`, maybeAtomLog(param)),
                      ),
                    )
                  } else if (call) {
                    let { payload } = call
                    if (isOnFulfill) {
                      payload = call.payload?.payload
                    }
                    logStack(payload, error, () => {
                      if (target !== log) {
                        params.forEach((param, i) =>
                          console.log(`param ${i + 1}:`, maybeAtomLog(param)),
                        )
                      }
                    })
                  }
                }
              }),
              'hook',
            )

            let prevState = top().state
            let state: typeof prevState
            let error: any

            try {
              state = next(...params)
            } catch (e) {
              error = e ?? new Error('unknown error')
              throw e
            }

            return state
          },
      ),
    )
  }

  // @ts-ignore TODO
  globalThis.__REATOM.push(logExt)

  log.extend(logExt)
}
